﻿namespace WLFramework.Reflection
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using WLFramework.Common;

    public static class ReflectionHelper
    {
        public static Type GetMemberType(this MemberInfo member)
        {
            Shield.AssertNotNull(ref member, "member");

            switch (member.MemberType)
            {
                case MemberTypes.Field: return (member as FieldInfo).FieldType;
                case MemberTypes.Property: return (member as PropertyInfo).PropertyType;
                case MemberTypes.Method: return (member as MethodInfo).ReturnType;
            }
            return null;
        }

        public static Type[] GetParameterTypes(this MethodBase method)
        {
            Shield.AssertNotNull(ref method, "method");
            return method.GetParameters().GetParameterTypes();
        }

        public static Type[] GetParameterTypes(this ParameterInfo[] ps)
        {
            Shield.AssertNotNull(ref ps, "ps");
            return (from p in ps
                    select p.ParameterType).ToArray();
        }

        internal static bool TryGetGenericInterfaceType(Type instanceType, Type targetOpenInterfaceType, out Type targetClosedInterfaceType)
        {
            // The interface must be open
            Shield.AssertIsTrue(targetOpenInterfaceType.IsInterface);
            Shield.AssertIsTrue(targetOpenInterfaceType.IsGenericTypeDefinition);
            Shield.AssertIsTrue(!instanceType.IsGenericTypeDefinition);

            // if instanceType is an interface, we must first check it directly
            if (instanceType.IsInterface &&
                instanceType.IsGenericType &&
                instanceType.GetGenericTypeDefinition() == targetOpenInterfaceType)
            {
                targetClosedInterfaceType = instanceType;
                return true;
            }

            try
            {
                // Purposefully not using FullName here because it results in a significantly
                //  more expensive component of GetInterface, this does mean that we're
                //  takign the chance that there aren't too many types which implement multiple
                //  interfaces by the same name...
                Type targetInterface = instanceType.GetInterface(targetOpenInterfaceType.Name, false);
                if (targetInterface != null &&
                    targetInterface.GetGenericTypeDefinition() == targetOpenInterfaceType)
                {
                    targetClosedInterfaceType = targetInterface;
                    return true;
                }
            }
            catch (AmbiguousMatchException)
            {
                // If there are multiple with the same name we should not pick any
            }

            targetClosedInterfaceType = null;
            return false;
        }

        #region Emit优化反射
        private static readonly Hashtable s_getterDict = Hashtable.Synchronized(new Hashtable(10240));
        private static readonly Hashtable s_setterDict = Hashtable.Synchronized(new Hashtable(10240));
        private static readonly Hashtable s_methodDict = Hashtable.Synchronized(new Hashtable(10240));

        private static readonly SafeDictionary<string, TargetCreater> _ctors =
            new SafeDictionary<string, TargetCreater>();

        public static object FastNew(this Type instanceType)
        {
            Shield.AssertNotNull(ref instanceType, "instanceType");

            TargetCreater creater;
            if (!_ctors.TryGetValue(instanceType.FullName, out creater))
            {
                creater = instanceType.CreateConstructor();
                if (!_ctors.Add(instanceType.FullName, creater))
                {
                    creater = _ctors[instanceType.FullName];
                }
            }

            return creater.Ctor();
        }

        public static object FastGetFieldValue(this FieldInfo fieldInfo, object obj)
        {
            if (fieldInfo == null)
                throw new ArgumentNullException("fieldInfo");

            GetValueDelegate getter = (GetValueDelegate)s_getterDict[fieldInfo];
            if (getter == null)
            {
                getter = DynamicDelegateFactory.CreateFieldGetterDelegate(fieldInfo);
                s_getterDict[fieldInfo] = getter;
            }

            return getter(obj);
        }

        public static void FastSetField(this FieldInfo fieldInfo, object obj, object value)
        {
            if (fieldInfo == null)
                throw new ArgumentNullException("fieldInfo");

            SetValueDelegate setter = (SetValueDelegate)s_setterDict[fieldInfo];
            if (setter == null)
            {
                setter = DynamicDelegateFactory.CreateFieldSetterDelegate(fieldInfo);
                s_setterDict[fieldInfo] = setter;
            }

            setter(obj, value);
        }

        public static object FastGetPropertyValue(this PropertyInfo propertyInfo, object obj)
        {
            if (propertyInfo == null)
                throw new ArgumentNullException("propertyInfo");

            GetValueDelegate getter = (GetValueDelegate)s_getterDict[propertyInfo];
            if (getter == null)
            {
                getter = DynamicDelegateFactory.CreatePropertyGetterDelegate(propertyInfo);
                s_getterDict[propertyInfo] = getter;
            }

            return getter(obj);
        }

        public static void FastSetProperty(this PropertyInfo propertyInfo, object obj, object value)
        {
            if (propertyInfo == null)
                throw new ArgumentNullException("propertyInfo");

            SetValueDelegate setter = (SetValueDelegate)s_setterDict[propertyInfo];
            if (setter == null)
            {
                setter = DynamicDelegateFactory.CreatePropertySetterDelegate(propertyInfo);
                s_setterDict[propertyInfo] = setter;
            }

            setter(obj, value);
        }

        public static object FastInvoke(this MethodInfo methodInfo, object obj, params object[] parameters)
        {
            if (methodInfo == null)
                throw new ArgumentNullException("methodInfo");

            MethodDelegate invoker = (MethodDelegate)s_methodDict[methodInfo];
            if (invoker == null)
            {
                invoker = DynamicDelegateFactory.CreateMethodDelegate(methodInfo);
                s_methodDict[methodInfo] = invoker;
            }

            return invoker(obj, parameters);
        }
        #endregion

        #region LambdaExpression
        public static string GetPropertyName(this Expression Expression)
        {
            MemberExpression TempExpression = Expression as MemberExpression;
            if (TempExpression == null)
                return "";
            return TempExpression.Expression.GetPropertyName() + TempExpression.Member.Name + ".";
        }

        public static string GetPropertyName(this LambdaExpression Expression)
        {
            if (Expression.Body is UnaryExpression && Expression.Body.NodeType == ExpressionType.Convert)
            {
                MemberExpression Temp = (MemberExpression)((UnaryExpression)Expression.Body).Operand;
                return GetPropertyName(Temp.Expression) + Temp.Member.Name;
            }
            if (!(Expression.Body is MemberExpression))
                throw new ArgumentException("Expression.Body is not a MemberExpression");
            return ((MemberExpression)Expression.Body).Expression.GetPropertyName() + ((MemberExpression)Expression.Body).Member.Name;
        }
        
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static Expression<Action<ClassType, DataType>> GetPropertyLambdaSetter<ClassType, DataType>(this Expression<Func<ClassType, DataType>> Property)
        {
            if (Property == null)
                throw new ArgumentNullException("Property");
            string PropertyName = Property.GetPropertyName();
            string[] SplitName = PropertyName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
            PropertyInfo PropertyInfo = typeof(ClassType).GetProperty(SplitName[0]);
            ParameterExpression ObjectInstance = Expression.Parameter(PropertyInfo.DeclaringType, "x");
            ParameterExpression PropertySet = Expression.Parameter(typeof(DataType), "y");
            MethodCallExpression SetterCall = null;
            MemberExpression PropertyGet = null;
            if (SplitName.Length > 1)
            {
                PropertyGet = Expression.Property(ObjectInstance, PropertyInfo);
                for (int x = 1; x < SplitName.Length - 1; ++x)
                {
                    PropertyInfo = PropertyInfo.PropertyType.GetProperty(SplitName[x]);
                    PropertyGet = Expression.Property(PropertyGet, PropertyInfo);
                }
                PropertyInfo = PropertyInfo.PropertyType.GetProperty(SplitName[SplitName.Length - 1]);
            }
            if (PropertyInfo.PropertyType != typeof(DataType))
            {
                UnaryExpression Convert = Expression.Convert(PropertySet, PropertyInfo.PropertyType);
                if (PropertyGet == null)
                    SetterCall = Expression.Call(ObjectInstance, PropertyInfo.GetSetMethod(), Convert);
                else
                    SetterCall = Expression.Call(PropertyGet, PropertyInfo.GetSetMethod(), Convert);
                return Expression.Lambda<Action<ClassType, DataType>>(SetterCall, ObjectInstance, PropertySet);
            }
            if (PropertyGet == null)
                SetterCall = Expression.Call(ObjectInstance, PropertyInfo.GetSetMethod(), PropertySet);
            else
                SetterCall = Expression.Call(PropertyGet, PropertyInfo.GetSetMethod(), PropertySet);
            return Expression.Lambda<Action<ClassType, DataType>>(SetterCall, ObjectInstance, PropertySet);
        }

        public static Expression<Action<ClassType, object>> GetPropertyLambdaSetter<ClassType>(this Expression<Func<ClassType, object>> Property)
        {
            return Property.GetPropertyLambdaSetter<ClassType, object>();
        }

        public static Expression<Func<ClassType, DataType>> GetPropertyLambdaGetter<ClassType, DataType>(this PropertyInfo Property)
        {
            if (!Property.PropertyType.IsOfType(typeof(DataType)))
                throw new ArgumentException("Property is not of the type specified");
            if (!Property.DeclaringType.IsOfType(typeof(ClassType)) && !typeof(ClassType).IsOfType(Property.DeclaringType))
                throw new ArgumentException("Property is not from the declaring class type specified");
            ParameterExpression ObjectInstance = Expression.Parameter(Property.DeclaringType, "x");
            MemberExpression PropertyGet = Expression.Property(ObjectInstance, Property);
            if (Property.PropertyType != typeof(DataType))
            {
                UnaryExpression Convert = Expression.Convert(PropertyGet, typeof(DataType));
                return Expression.Lambda<Func<ClassType, DataType>>(Convert, ObjectInstance);
            }
            return Expression.Lambda<Func<ClassType, DataType>>(PropertyGet, ObjectInstance);
        }

        public static Expression<Func<ClassType, object>> GetPropertyLambdaGetter<ClassType>(this PropertyInfo Property)
        {
            return Property.GetPropertyLambdaGetter<ClassType, object>();
        }
        #endregion
    }
}
